package com.bupt.kgplatform.controller;

import com.alibaba.fastjson.JSONObject;
import com.bupt.kgplatform.common.RestTemplateUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>高血压辅助决策</p>
 * @author yql
 */

@RestController
@RequestMapping("/kgplatform/hypertension")
public class HypertensionController {
    private final static String tugraphUrl = "http://114.67.200.41:7070";
    private final static String graphName = "hypertension";

    private final static class DrugScore {
        public final static int CONTRADICTION = -53;
        public final static int CAUTION = -17;
        public final static int CONTRADICTION_CONFIRM = 13;
        public final static int CONTRADICTION_MAY = 5;
        public final static int CONTRADICTION_NOT = 1;
        public final static int ADDITION_CONFIRM = 13;
        public final static int ADDITION_MAY = 5;
        public final static int ADDITION_NOT = 1;
    }

    private static class Drug{
        public String name;
        public String level;
        public float score = 0;
        public Drug sup;
        public List<Drug> sub = new ArrayList<>();
        public boolean isNew;
//        public String indicateType;
//        public String indicateRank;
        public Set<HashMap<String, String>> indications = new HashSet<>();

        public Set<Object> packagedIndications = new HashSet<>();

        public static final String LEVEL_CLASS = "drug_class";
        public static final String LEVEL_TYPE = "drug_type";
        public static final String LEVEL_NAME = "drug_name";

//        public Drug(){}

        public Drug(String name, String level, float score){
            this.name = name;
            this.level = level;
            this.score = score;
            this.isNew = true;
        }

//        public Drug(String name, String level, String indicateType, String indicateRank){
//            this.name = name;
//            this.level = level;
//            this.indicateType = indicateType;
//            this.indicateRank = indicateRank;
//            this.isNew = false;
//        }

        public Drug(String name, String level, String indicateType, String indicateRank){
            this.name = name;
            this.level = level;

            HashMap<String, String> indication = new HashMap<>();
            indication.put("indicateType", indicateType);
            indication.put("indicateRank", indicateRank);
            this.indications.add(indication);
        }

        public Drug(String name, String level, Set<HashMap<String, String>> indications){
            this.name = name;
            this.level = level;
            this.indications = indications;
        }

        public String getChineseLevel(){
            String result = "";
            switch (this.level){
                case LEVEL_CLASS:
                    result = "药品类别";
                    break;
                case LEVEL_TYPE:
                    result = "药品类型";
                    break;
                case LEVEL_NAME:
                    result = "药品化学名";
                    break;
            }
            return result;
        }

        public void addIndication(String indicateType, String indicateRank){
            HashMap<String, String> indication = new HashMap<>();
            indication.put("indicateType", indicateType);
            indication.put("indicateRank", indicateRank);
            this.indications.add(indication);
        }
    }

    private static class CombinedDrug{
        public List<Drug> drugs;
        public float score;
        public String indicateType;
        public String indicateRank;

        public static final float DELTA = 0.5f;
    }

    /**
     * <p>配置请求头部</p>
     * @return 请求头部
     */
    private static HttpHeaders getHeader(){
        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.ACCEPT,"application/json; charset=UTF-8");
        headers.set(HttpHeaders.CONTENT_TYPE,"application/json");
        headers.set("server_version","-1");
        Map<Object, Object> map= new HashMap<>();
        map.put("user", "admin");
        map.put("password", "73@TuGraph");

        JSONObject res = RestTemplateUtils.post("http://114.67.200.41:7070/login",headers,JSONObject.toJSONString(map),JSONObject.class,(Object) null).getBody();
        assert res != null;
        String token = (String) res.get("jwt");

        headers = new HttpHeaders();
        headers.set(HttpHeaders.AUTHORIZATION, "Bearer " + token);
        headers.set(HttpHeaders.ACCEPT,"application/json; charset=UTF-8");
        headers.set(HttpHeaders.CONTENT_TYPE,"application/json");
        headers.set("server_version","-1");
        return headers;
    }

    /**
     * <p>执行Cypher脚本</p>
     * @param graph 图名
     * @param script 脚本
     * @return 数据库返回的json
     */
    private static JSONObject exeScript(String graph, String script){
        JSONObject reqBody = new JSONObject();
        reqBody.put("graph", graph);
        reqBody.put("script", script);
        JSONObject res = RestTemplateUtils.post("http://114.67.200.41:7070/cypher",getHeader(),reqBody,JSONObject.class).getBody();
        return res;
    }

    /**
     * <p>判断数值指标是否符合适应症规定范围</p>
     * @param number 患者指标
     * @param range 规定范围，如">=100"
     * @return 是否符合
     */
    private static boolean isExpressionTrue(float number, String range){
        String operator;
        float rangeNum;
        if(range.getBytes()[1] == '='){
            operator = range.substring(0, 1);
            rangeNum = Float.parseFloat(range.substring(2));
        }
        else{
            operator = range.substring(0, 0);
            rangeNum = Float.parseFloat(range.substring(1));
        }
        switch (operator) {
            case ">=":
                return number >= rangeNum;
            case "<=":
                return number <= rangeNum;
            case "<":
                return number < rangeNum;
            case ">":
                return number > rangeNum;

            default:
                return false;
        }
    }

    /**
     * <p>从适应症中筛去不包含在描述范围内的项目</p>
     * @param conditions 适应症
     * @param descriptionType 描述类型，如“disease”
     * @param descriptions 描述组合
     */
    private static void filterCondition(List<String> conditions, String descriptionType, Object descriptions){
        String getConditionDescriptionScript = "MATCH (c:condition)-[:condition_" + descriptionType +
                "]->(d:" + descriptionType + ") WHERE ";
        for (String condition : conditions){
            getConditionDescriptionScript += "c.id=\"" + condition + "\" or ";
        }
        getConditionDescriptionScript = getConditionDescriptionScript.substring(0, getConditionDescriptionScript.length() - 3);
        getConditionDescriptionScript += "RETURN c.id, d.name";
        Object getConditionDescription = exeScript(graphName, getConditionDescriptionScript).get("result");
        for(List<String> getConditionDescriptionItem : (List<List<String>>)getConditionDescription){
            switch (descriptionType) {
                case "numeric_index":
                    // 解析图谱中存储的数值指标
                    String numericIndexName = getConditionDescriptionItem.get(1);
                    String[] numericIndexNameItems = numericIndexName.split(":");
                    String nameUnit = numericIndexNameItems[0];
                    String range = numericIndexNameItems[1];

                    // 对应找到患者情况中的数值指标
                    String number = (String)((HashMap<String, Object>)descriptions).get(nameUnit);
                    // 若指标不存在或超出范围，则不成立
                    if(number == null || !isExpressionTrue(Float.parseFloat(number), range)){
                        conditions.remove(getConditionDescriptionItem.get(0));
                    }
                    break;
                default:
                    if(!((Set<String>)descriptions).contains(getConditionDescriptionItem.get(1))){
                        conditions.remove(getConditionDescriptionItem.get(0));
                    }
            }
        }
    }


    /**
     * <p>打包适应症数据供前端使用</p>
     * @param condition 适应症
     * @param indicateType 适应类型
     * @param indicateRank 适应等级
     * @param drugs 对应药品
     * @return 打包好的适应症数据
     */
    private static HashMap<Object, Object> packageIndication(String condition, String indicateType,
                                                             String indicateRank, List<Drug> drugs){
        HashMap<Object, Object> result = new HashMap<>();
        // 找到方案对应疾病症状等患者状况信息
        String getIndicationConditionScript = "MATCH (c:condition)-[]->(n)" +
                " WHERE c.id=\"" + condition + "\" RETURN n, n.name";
        Object getIndicationCondition = exeScript(graphName, getIndicationConditionScript).get("result");
        String conditionDescription = "";
        String patientAttDescription = "";
        for(List<String> getIndicationConditionItem : (List<List<String>>)getIndicationCondition){
            JSONObject indicationCondition = JSONObject.parseObject(getIndicationConditionItem.get(0));
            String label = (String) indicationCondition.get("label");
            String name = getIndicationConditionItem.get(1);
            switch (label){
                case "disease":
                    conditionDescription += name + "(疾病),";
                    break;
                case "symptom":
                    conditionDescription += name + "(症状),";
                    break;
                case "numeric_index" :
                    patientAttDescription += name + ",";
                    break;
                case "physiological_status" :
                    patientAttDescription += name + ",";
                    break;
            }
        }
        if(!conditionDescription.isEmpty()){
            conditionDescription = conditionDescription.substring(0, conditionDescription.length() - 1);
        }
        if(!patientAttDescription.isEmpty()){
            patientAttDescription = patientAttDescription.substring(0, patientAttDescription.length() - 1);
        }
        result.put("condition", conditionDescription);
        result.put("patient_att", patientAttDescription);

        result.put("relation", indicateType);
        if (indicateRank != null && !indicateRank.isEmpty()){
            result.put("relation_att", "适应等级:" + indicateRank);
        }
        else{
            result.put("relation_att", "");
        }

        String drugsDescription = "";
        for(Drug drug : drugs){
            drugsDescription += drug.name + "(" + drug.getChineseLevel() + ")";
        }
        result.put("object", drugsDescription);

        return result;
    }

    /**
     * <p>根据药品适应类型和等级判断其分数</p>
     * @param indicateType 适应类型
     * @param indicateRank 适应等级
     * @return 药品分数
     */
    private static float getDrugScore(String indicateType, String indicateRank){
        float result = 0;
        switch (indicateType) {
            case "适应药品":
                switch (indicateRank) {
                    case "适用":
                        result = DrugScore.CONTRADICTION_CONFIRM;
                        break;
                    case "可能适用":
                        result = DrugScore.CONTRADICTION_MAY;
                        break;
                    case "证据不足或不适用":
                        result = DrugScore.CONTRADICTION_NOT;
                        break;
                }
                break;
            case "增加药品":
                switch (indicateRank) {
                    case "适用":
                        result = DrugScore.ADDITION_CONFIRM;
                        break;
                    case "可能适用":
                        result = DrugScore.ADDITION_MAY;
                        break;
                    case "证据不足或不适用":
                        result = DrugScore.ADDITION_NOT;
                        break;
                }
                break;
            case "慎用药品":
                result = DrugScore.CAUTION;
                break;
            case "禁忌药品":
                result = DrugScore.CONTRADICTION;
                break;

            default:
                result = 0;
        }
        return result;
    }

    /**
     * <p>获取信息填写页面所需的疾病类型等动态数据</p>
     * @return 按json打包好的数据
     */
    @GetMapping("/input_item")
    public ResponseEntity<Object> getInputItem(){
        try {
            HashMap<Object, Object> result = new HashMap<>();

//            JSONObject json = TugraphUtil.getGraphNodesAllWithCypher(graphName);
//            System.out.println(json);

            // 获取药品类别
            String getDrugClassScript = "MATCH (drug_class:drug_class) RETURN drug_class.name";
            Object drugClassResponse = exeScript(graphName, getDrugClassScript).get("result");
            List<Object> drugClass = new ArrayList<>();
            for(List<Object> drugClassItem : (List<List<Object>>)drugClassResponse){
                drugClass.add(drugClassItem.get(0));
            }
            result.put("药品类别", drugClass);

            // 获取药品类型
            String getDrugTypeScript = "MATCH (drug_type:drug_type) RETURN drug_type.name";
            Object drugTypeResponse = exeScript(graphName, getDrugTypeScript).get("result");
            List<Object> drugType = new ArrayList<>();
            for(List<Object> drugTypeItem : (List<List<Object>>)drugTypeResponse){
                drugType.add(drugTypeItem.get(0));
            }
            result.put("药品类型", drugType);

            // 获取药品化学名
            String getDrugNameScript = "MATCH (drug_name:drug_name) RETURN drug_name.name";
            Object drugNameResponse = exeScript(graphName, getDrugNameScript).get("result");
            List<Object> drugName = new ArrayList<>();
            for(List<Object> drugNameItem : (List<List<Object>>)drugNameResponse){
                drugName.add(drugNameItem.get(0));
            }
            result.put("药品化学名", drugName);

            // 获取疾病
            String getDiseaseScript = "MATCH (disease:disease) RETURN disease.name";
            Object diseaseResponse = exeScript(graphName, getDiseaseScript).get("result");
            List<Object> disease = new ArrayList<>();
            for(List<Object> diseaseItem : (List<List<Object>>)diseaseResponse){
                disease.add(diseaseItem.get(0));
            }
            result.put("疾病", disease);

            // 获取症状
            String getSymptomScript = "MATCH (symptom:symptom) RETURN symptom.name";
            Object symptomResponse = exeScript(graphName, getSymptomScript).get("result");
            List<Object> symptom = new ArrayList<>();
            for(List<Object> symptomItem : (List<List<Object>>)symptomResponse){
                symptom.add(symptomItem.get(0));
            }
            result.put("症状", symptom);

            // 获取数值指标
            HashMap<String, Set<String>> numericAttribute = new HashMap<>();
            String getNumericIndexScript = "MATCH (n:numeric_index) RETURN n.name";
            Object numericIndex = exeScript(graphName, getNumericIndexScript).get("result");
            for(List<String> numericIndexItem : (List<List<String>>)numericIndex){
                // 解析单位，格式为"name(unit):range"
                Pattern pattern = Pattern.compile("(?<name>.*)\\((?<unit>.*)\\):(?<range>.*)");
                Matcher matcher = pattern.matcher(numericIndexItem.get(0));
                if(matcher.find()){
                    String name = matcher.group("name");
                    String unit = matcher.group("unit");
                    Set<String> units = numericAttribute.get(name);
                    if(units == null){
                        units = new HashSet<>();
                        units.add(unit);
                        numericAttribute.put(name, units);
                    }
                    else{
                        units.add(unit);
                    }
                }
            }
            result.put("数值型属性", numericAttribute);

            // 获取文本数据
            List<Object> textAttribute = new ArrayList<>();
            String getPhysiologicalStatusScript = "MATCH (n:physiological_status) RETURN n.name";
            Object physiologicalStatus = exeScript(graphName, getPhysiologicalStatusScript).get("result");
            for(List<Object> physiologicalStatusItem : (List<List<Object>>)physiologicalStatus){
                // 解析单位，","隔开
                textAttribute.add(physiologicalStatusItem.get(0));
            }
            result.put("文本型属性", textAttribute);

            // 添加分类数据
            List<Object> female = new ArrayList<>();
            female.add("初产妇");
            female.add("妊娠间隔>10年");
            female.add("多胎妊娠");
            List<Object> historical = new ArrayList<>();
            historical.add("既往妊娠合并慢性肾病");
            historical.add("既往妊娠合并自身免疫病");
            historical.add("既往妊娠合并高血压");
            historical.add("既往妊娠合并糖尿病");
            historical.add("既往妊娠合并慢性高血压");
            HashMap<Object, Object> labelClassification = new HashMap<>();
            labelClassification.put("female", female);
            labelClassification.put("historical", historical);
            result.put("label_classification", labelClassification);

            // 添加互斥标签数据
            List<Object> mutualExclusive = new ArrayList<>();
            List<Object> mutualExclusiveItem1 = new ArrayList<>();
            mutualExclusiveItem1.add("有残存肾功能");
            mutualExclusiveItem1.add("无残存肾功能");
            mutualExclusive.add(mutualExclusiveItem1);
            List<Object> mutualExclusiveItem2 = new ArrayList<>();
            mutualExclusiveItem2.add("NYHA心功能II级");
            mutualExclusiveItem2.add("NYHA心功能III级");
            mutualExclusiveItem2.add("NYHA心功能IV级");
            mutualExclusive.add(mutualExclusiveItem2);
            result.put("mutual_exclusive", mutualExclusive);

            HashMap<Object, Object> order = new HashMap<>();
            List<Object> pressureOrder = new ArrayList<>();
            pressureOrder.add("SBP");
            pressureOrder.add("DBP");
            order.put("pressure_order", pressureOrder);
            result.put("order", order);

            return new ResponseEntity<>(result, HttpStatus.OK);
        }catch (Exception e){
            e.printStackTrace();
            HashMap<String, Object> response = new HashMap<>();
            response.put("msg", "服务器错误");
            return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * <p>根据输入的患者信息，结合知识图谱进行药品评分</p>
     * @param patientCondition 患者情况
     * @return 按json打包好的药品评分等信息
     */
    @PostMapping("/prescribe")
    public ResponseEntity<Object> getPrescribe(@RequestBody HashMap<Object, Object> patientCondition){
        try {
//            System.out.println(patientCondition);

            // 取出前端发来的数据
            Set<String> patientDiseases = new HashSet<>((List<String>)patientCondition.get("疾病"));
            Set<String> patientSymptoms = new HashSet<>((List<String>)patientCondition.get("症状"));
            Set<String> patientPhysiologicalStatus = new HashSet<>((List<String>)patientCondition.get("文本型属性"));
            HashMap<String, Object> patientNumericIndices = (HashMap<String, Object>)patientCondition.get("数值型属性");
            Set<String> patientDrugClasses = new HashSet<>((List<String>)patientCondition.get("药品类别"));
            Set<String> patientDrugTypes = new HashSet<>((List<String>)patientCondition.get("药品类型"));
            Set<String> patientDrugNames = new HashSet<>((List<String>)patientCondition.get("药品化学名"));

            // 对疾病进行同义替换
            // 队列存储中间结果，便于计算多级同义替换
            StringBuilder getDiseaseSynonymScript = new StringBuilder("MATCH (d:disease)-[:disease_synonym*1..]->(di:disease) WHERE ");
            for(String disease : patientDiseases){
                getDiseaseSynonymScript.append("d.name=\"").append(disease).append("\" or ");
            }
            getDiseaseSynonymScript = new StringBuilder(getDiseaseSynonymScript.substring(0, getDiseaseSynonymScript.length() - 3));
            getDiseaseSynonymScript.append("RETURN DISTINCT di.name");
            Object getDiseaseSynonym = exeScript(graphName, getDiseaseSynonymScript.toString()).get("result");
            for(List<String> getDiseaseSynonymItem : (List<List<String>>)getDiseaseSynonym){
                patientDiseases.add(getDiseaseSynonymItem.get(0));
            }

            // 记录疾病层级关系
            List<Object> patientDiseaseHierarchy = new ArrayList<>();
            // 找到所有父类疾病，添加进患者状况
            StringBuilder getSuperDiseasesScript = new StringBuilder("MATCH (d:disease)-[:disease_subclass*1..]->(di:disease) WHERE ");
            for(String disease :patientDiseases){
                getSuperDiseasesScript.append("d.name=\"").append(disease).append("\" or ");
            }
            getSuperDiseasesScript = new StringBuilder(getSuperDiseasesScript.substring(0, getSuperDiseasesScript.length() - 3));
            getSuperDiseasesScript.append("RETURN DISTINCT di.name");
            Object getSuperDiseases = exeScript(graphName, getSuperDiseasesScript.toString()).get("result");
            for(List<String> getSuperDiseasesItem : (List<List<String>>)getSuperDiseases){
                patientDiseases.add(getSuperDiseasesItem.get(0));
            }

            // 根据患者症状组合，寻找合适的适应症或禁忌症
            // 要求患者的情况集合完全包含对应描述集合，已有描述集合存在患者所未提供的症状则忽略
            // 获取所有适应症
            String getConditionsScript = "MATCH (c:condition) RETURN c.id";
            Object getConditions = exeScript(graphName, getConditionsScript).get("result");
            List<String> conditions = new ArrayList<>();
            for(List<String> getConditionsItem : (List<List<String>>)getConditions){
                conditions.add(getConditionsItem.get(0));
            }

            // 筛去不适合的适应症
            filterCondition(conditions, "disease", patientDiseases);
            filterCondition(conditions, "symptom", patientSymptoms);
            filterCondition(conditions, "numeric_index", patientNumericIndices);
            filterCondition(conditions, "physiological_status", patientPhysiologicalStatus);
            filterCondition(conditions, "drug_class", patientDrugClasses);
            filterCondition(conditions, "drug_type", patientDrugTypes);
            filterCondition(conditions, "drug_name", patientDrugNames);

            // 根据合适的适应症，找到对应药物组合
            // 药物集合
            HashMap<String, Drug> drugClasses = new HashMap<>();
            HashMap<String, Drug> drugTypes = new HashMap<>();
            HashMap<String, Drug> drugNames = new HashMap<>();
//            HashMap<String, Drug> drugs = new HashMap<>();
            // 联合用药
//            List<CombinedDrug> combinedDrugs = new ArrayList<>();
            HashMap<String, CombinedDrug> combinedDrugs = new HashMap<>();

            // 记录用药方案及其适应症以反馈至前端
            Set<Object> patientIndication = new HashSet<>();
            // 记录已经遍历过的用药方案
            HashMap<String, List<Drug>> drugRegimens = new HashMap<>();
            // 遍历适应症及其对应的用药方案
            for(String condition : conditions){
                // 找到用药方案
                String getDrugRegimensScript = "MATCH (c:condition)-[i:indicate]->(d:drug_regimen)" +
                        " WHERE c.id=\"" + condition + "\" RETURN i.type, i.rank, d.id";
                Object getDrugRegimens = exeScript(graphName, getDrugRegimensScript).get("result");

                for(List<String> getDrugRegimen : (List<List<String>>)getDrugRegimens){
                    String indicateType = getDrugRegimen.get(0);
                    String indicateRank = getDrugRegimen.get(1);
                    String drugRegimen = getDrugRegimen.get(2);

                    // 若方案已被查询过，直接使用
                    List<Drug> drugRegimenDrugs = drugRegimens.get(drugRegimen);
                    if (drugRegimenDrugs == null){
                        drugRegimenDrugs = new ArrayList<>();

                        // 找到方案对应药物，记录
                        String getDrugRegimenDrugsScript = "MATCH (dr:drug_regimen)-[]->(d)" +
                                " WHERE dr.id=\"" + drugRegimen + "\" RETURN d, d.name";
                        Object getDrugRegimenDrugs = exeScript(graphName, getDrugRegimenDrugsScript).get("result");

                        for(List<String> getDrugRegimenDrugsItem : (List<List<String>>)getDrugRegimenDrugs){
                            JSONObject drugRegimenDrug = JSONObject.parseObject(getDrugRegimenDrugsItem.get(0));
                            String level = (String) drugRegimenDrug.get("label");
                            String name = getDrugRegimenDrugsItem.get(1);

                            HashMap<String, Drug> drugs;
                            switch (level){
                                case Drug.LEVEL_CLASS:
                                    drugs = drugClasses;
                                    break;
                                case Drug.LEVEL_TYPE:
                                    drugs = drugTypes;
                                    break;
                                case Drug.LEVEL_NAME:
                                    drugs = drugNames;
                                    break;
                                default:
                                    drugs = new HashMap<>();
                            }

                            Drug drug = drugs.get(name);
                            if(drug == null){
                                drug = new Drug(name, level, indicateType, indicateRank);
                                drugs.put(name, drug);
                            }
                            else{
                                drug.addIndication(indicateType, indicateRank);
                            }
                            drugRegimenDrugs.add(drug);

//                    drugs.put(name, drug);
                        }

                        // 记录新的用药方案
                        drugRegimens.put(drugRegimen, drugRegimenDrugs);
                    }

                    // 若存在联合用药，记录联合用药
                    if(drugRegimenDrugs.size() > 1 && !combinedDrugs.containsKey(drugRegimen)){
                        CombinedDrug combinedDrug = new CombinedDrug();
                        // 记录药品
                        combinedDrug.drugs = drugRegimenDrugs;
                        // 记录适应症情况
                        combinedDrug.indicateType = indicateType;
                        combinedDrug.indicateRank = indicateRank;

                        combinedDrugs.put(drugRegimen, combinedDrug);
                    }

                    // 记录
//                    HashMap<Object, Object> packagedIndication = packageIndication(condition, indicateType, indicateRank, drugRegimenDrugs);
//                    for(Drug drug : drugRegimenDrugs){
//                        drug.indications.add(packagedIndication);
//                    }
//                    patientIndication.add(packagedIndication);
                    HashMap<Object, Object> packagedIndication = packageIndication(condition, indicateType,
                            indicateRank, drugRegimenDrugs);
                    for(Drug drug : drugRegimenDrugs){
                        drug.packagedIndications.add(packagedIndication);
                    }
                    patientIndication.add(packagedIndication);
                }

            }

            // 计算药物评分（这里指药物方案中的药物）
            // 药物评分为，其自身评分+父类评分和
            // 对于未出现在用药方案中的评分，其分数为子类药物平均
            // 计算所有药物评分
            for(Drug drugClass : drugClasses.values()){
                for (HashMap<String, String> indication : drugClass.indications){
                    drugClass.score += getDrugScore(indication.get("indicateType"),
                            indication.get("indicateRank"));
                }
            }
            for(Drug drugType : drugTypes.values()){
                for (HashMap<String, String> indication : drugType.indications){
                    drugType.score += getDrugScore(indication.get("indicateType"),
                            indication.get("indicateRank"));
                }
            }
            for(Drug drugName : drugNames.values()){
                for (HashMap<String, String> indication : drugName.indications){
                    drugName.score += getDrugScore(indication.get("indicateType"),
                            indication.get("indicateRank"));
                }
            }

            // 分数自顶向下传播
            // 将药品类别分数加至其下属药品类型和药品化学名
            for(Drug drugClass : drugClasses.values()){
                String getDrugClassSubClassScript = "MATCH (dc:drug_class)<-[:drug_class_subclass]-(dt:drug_type)"
                        + " WHERE dc.name=\"" + drugClass.name + "\" RETURN DISTINCT dt.name";
                Object getDrugClassSubClass = exeScript(graphName, getDrugClassSubClassScript).get("result");
                for(List<String> getDrugClassSubClassItem : (List<List<String>>)getDrugClassSubClass){
                    String name = getDrugClassSubClassItem.get(0);
                    Drug drugType = drugTypes.get(name);
                    // 不存在则新建
                    if(drugType == null){
                        drugType = new Drug(name, Drug.LEVEL_TYPE, drugClass.indications);
                        drugTypes.put(name, drugType);

                        // 继承适应症关系
                        drugType.packagedIndications.addAll(drugClass.packagedIndications);
                    }
                    // 存在则叠加分数
                    drugType.score += drugClass.score;

                    // 建立树形关系
                    drugClass.sub.add(drugType);
                    drugType.sup = drugClass;
                }
            }
            // 将药品类型分数加至其下属药品化学名
            for(Drug drugType : drugTypes.values()){
                String getDrugTypeSubClassScript = "MATCH (dt:drug_type)<-[:drug_type_subclass]-(dn:drug_name)"
                        + " WHERE dt.name=\"" + drugType.name + "\" RETURN DISTINCT dn.name";
                Object getDrugTypeSubClass = exeScript(graphName, getDrugTypeSubClassScript).get("result");
                for(List<String> getDrugTypeSubClassItem : (List<List<String>>)getDrugTypeSubClass){
                    String name = getDrugTypeSubClassItem.get(0);
                    Drug drugName = drugNames.get(name);
                    // 不存在则新建
                    if(drugName == null){
                        drugName = new Drug(name, Drug.LEVEL_NAME, drugType.indications);
                        drugNames.put(name, drugName);

                        // 继承适应症关系
                        drugName.packagedIndications.addAll(drugType.packagedIndications);
                    }
                    // 存在则叠加分数
                    drugName.score += drugType.score;

                    // 建立树形关系
                    drugType.sub.add(drugName);
                    drugName.sup = drugType;
                }
            }

            // 分数自底向上传播
            // 寻找未出现的药品类型，其分数为下属药品化学名分数平均
            for(Drug drugName : drugNames.values()){
                Drug drugType = drugName.sup;
                // 不存在其父类，新建
                if(drugType == null){
                    String getDrugNameSupScript = "MATCH (dn:drug_name)-[:drug_type_subclass]->(dt:drug_type) " +
                            " WHERE dn.name=\"" + drugName.name + "\" RETURN dt.name";
                    Object getDrugNameSup = exeScript(graphName, getDrugNameSupScript).get("result");
//                    System.out.println(getDrugNameSupScript);

                    String drugTypeName;
                    if(((List<List<String>>)getDrugNameSup).isEmpty()){
                        drugTypeName = "其他类型";
                    }
                    else{
                        drugTypeName = ((List<List<String>>)getDrugNameSup).get(0).get(0);
                    }

                    // 寻找对应药物类型
                    drugType = drugTypes.get(drugTypeName);
                    // 不存在则新建
                    if(drugType == null){
                        drugType = new Drug(drugTypeName, Drug.LEVEL_TYPE, drugName.score);
                        drugTypes.put(drugTypeName, drugType);
                    }
                    // 存在则累计求平均
                    else if(drugType.isNew){
                        drugType.score = (drugType.score * (drugType.sub.size()-1) + drugName.score) / drugType.sub.size();
                    }
                    // 建立树形结构
                    drugName.sup = drugType;
                    drugType.sub.add(drugName);

                    // 继承适应症关系
                    drugType.packagedIndications.addAll(drugName.packagedIndications);
                }
            }

            // 寻找未出现的药品类别，其分数为下属药品类型分数平均
            for(Drug drugType : drugTypes.values()){
                Drug drugClass = drugType.sup;
                // 不存在其父类，新建
                if(drugClass == null){
                    String getDrugTypeSupScript = "MATCH (dt:drug_type)-[:drug_class_subclass]->(dc:drug_class) " +
                            " WHERE dt.name=\"" + drugType.name + "\" RETURN dc.name";
                    Object getDrugTypeSup = exeScript(graphName, getDrugTypeSupScript).get("result");
                    String drugClassName;
                    if(((List<List<String>>)getDrugTypeSup).isEmpty()){
                        drugClassName = "其他类别";
                    }
                    else{
                        drugClassName = ((List<List<String>>)getDrugTypeSup).get(0).get(0);
                    }

                    // 寻找对应药物类型
                    drugClass = drugClasses.get(drugClassName);
                    // 不存在则新建
                    if(drugClass == null){
                        drugClass = new Drug(drugClassName, Drug.LEVEL_CLASS, drugType.score);
                        // 记录至药物集合
                        drugClasses.put(drugClassName, drugClass);
                    }
                    else if(drugClass.isNew){
                        drugClass.score = (drugClass.score * (drugClass.sub.size()-1) + drugType.score) / drugClass.sub.size();
                    }
                    // 建立树形结构
                    drugType.sup = drugClass;
                    drugClass.sub.add(drugType);

                    // 继承适应症关系
                    drugClass.packagedIndications.addAll(drugType.packagedIndications);
                }
            }

            // 计算联合用药评分
            // 联合用药评分为，联合用药评分+涉及药物评分和*δ
            // 联合用药评分为其对应适应症属性对应评分，涉及药物评分中，适应药物评分需除以联合药品数，禁忌药物评分不变
            for(CombinedDrug combinedDrug : combinedDrugs.values()) {
                combinedDrug.score = getDrugScore(combinedDrug.indicateType, combinedDrug.indicateRank);
                float drugsScore = 0;
                for(Drug drug : combinedDrug.drugs){
                    drugsScore += drug.score;
                }
                combinedDrug.score += drugsScore * CombinedDrug.DELTA;
            }

            // 整合结论，发往前端
            HashMap<Object, Object> result = new HashMap<>();
            // 药品关系树
            List<Object> medication = new ArrayList<>();

            for(Drug drugClass : drugClasses.values()){
                List<Object> drugClassChildren = new ArrayList<>();
                for(Drug drugType : drugClass.sub){
                    List<Object> drugTypeChildren = new ArrayList<>();
                    for(Drug drugName : drugType.sub){
                        HashMap<Object, Object> drugNameNode = new HashMap<>();
                        drugNameNode.put("name", drugName.name);
                        drugNameNode.put("level", drugName.getChineseLevel());
                        drugNameNode.put("score", drugName.score);
                        drugNameNode.put("children", new ArrayList<>());
                        drugTypeChildren.add(drugNameNode);
                    }
                    HashMap<Object, Object> drugTypeNode = new HashMap<>();
                    drugTypeNode.put("name", drugType.name);
                    drugTypeNode.put("level", drugType.getChineseLevel());
                    drugTypeNode.put("score", drugType.score);
                    drugTypeNode.put("children", drugTypeChildren);
                    drugClassChildren.add(drugTypeNode);
                }
                HashMap<Object, Object> drugClassNode = new HashMap<>();
                drugClassNode.put("name", drugClass.name);
                drugClassNode.put("level", drugClass.getChineseLevel());
                drugClassNode.put("score", drugClass.score);
                drugClassNode.put("children", drugClassChildren);
                medication.add(drugClassNode);
            }

            result.put("medication", medication);

            // 联合用药
            List<Object> combined = new ArrayList<>();
            for (CombinedDrug combinedDrug : combinedDrugs.values()){
                String combinedDrugName = "";
                for(Drug drug : combinedDrug.drugs){
                    combinedDrugName += drug.name + "(" + drug.getChineseLevel() + ")+";
                }
                if(!combinedDrugName.isEmpty()){
                    combinedDrugName = combinedDrugName.substring(0, combinedDrugName.length() - 1);
                }
                HashMap<Object, Object> combinedDrugDescription = new HashMap<>();
                combinedDrugDescription.put("regimen", combinedDrugName);
                combinedDrugDescription.put("score", combinedDrug.score);
                combined.add(combinedDrugDescription);
            }

            result.put("combined", combined);

            // 患者情况
            HashMap<Object, Object> patient = new HashMap<>();

            // 适应症
            patient.put("indication", patientIndication);

            List<Object> patientContraindication = new ArrayList<>();

            // 禁忌症（非必要）
            patient.put("contraindication", patientContraindication);

            // 患者疾病层级关系
            patient.put("disease_hierarchy", patientDiseaseHierarchy);

            List<Object> patientDiseaseSynonym = new ArrayList<>();

            patient.put("disease_synonym", patientDiseaseSynonym);

            result.put("patient", patient);

            // 适应症项目
            HashMap<Object, List<Object>> reasons = new HashMap<>();
            for (Drug drugClass : drugClasses.values()){
                List<Object> reasonsItem = new ArrayList<>();
//                HashMap<Object, Object> indication = new HashMap<>();
//                indication.put("relation", drugClass.indicateType);
//                indication.put("relation_att", "适应等级" + drugClass.indicateRank);
//                reasonsItem.add(indication);

                reasonsItem.addAll(drugClass.packagedIndications);

                reasons.put(drugClass.name, reasonsItem);
            }
            for (Drug drugType : drugTypes.values()){
                List<Object> reasonsItem = new ArrayList<>();
//                HashMap<Object, Object> indication = new HashMap<>();
//                indication.put("relation", drugType.indicateType);
//                indication.put("relation_att", "适应等级" + drugType.indicateRank);
//                reasonsItem.add(indication);

                reasonsItem.addAll(drugType.packagedIndications);

                HashMap<Object, Object> subclass = new HashMap<>();
                subclass.put("关系", "子类");
                subclass.put("药品类别", drugType.sup.name);
                subclass.put("药品类型", drugType.name);
                reasonsItem.add(subclass);

                reasons.put(drugType.name, reasonsItem);
            }
            for (Drug drugName : drugNames.values()){
                List<Object> reasonsItem = new ArrayList<>();
//                HashMap<Object, Object> indication = new HashMap<>();
//                indication.put("relation", drugName.indicateType);
//                indication.put("relation_att", "适应等级" + drugName.indicateRank);
//                reasonsItem.add(indication);
                reasonsItem.addAll(drugName.packagedIndications);

                HashMap<Object, Object> subclass = new HashMap<>();
                subclass.put("关系", "子类");
//                System.out.println(drugName.name);System.out.println(drugName.sup);
                subclass.put("药品类别", drugName.sup.sup.name);
                subclass.put("药品类型", drugName.sup.name);
                reasonsItem.add(subclass);

                subclass = new HashMap<>();
                subclass.put("关系", "实例");
                subclass.put("药品化学名", drugName.name);
                subclass.put("药品类型", drugName.sup.name);
                reasonsItem.add(subclass);

                reasons.put(drugName.name, reasonsItem);
            }

            result.put("reasons", reasons);
            return new ResponseEntity<>(result, HttpStatus.OK);
        }catch (Exception e){
            e.printStackTrace();
            HashMap<String, Object> response = new HashMap<>();
            response.put("msg", "服务器错误");
            return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}
